<?php defined('SYSPATH') or die('No direct script access.');

/**
 * Basecamp Service Driver Library
 * http://developer.37signals.com/basecamp/
 *
 * $Id$
 *
 * @package		Service Module
 * @author		Alex Sancho
 * @copyright	(c) 2008 Alex Sancho
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in the
 *	  documentation and/or other materials provided with the distribution.
 * 3. Neither the name of copyright holders nor the names of its
 *	  contributors may be used to endorse or promote products derived
 *	  from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.	IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

class Service_Basecamp_Driver extends Service_Driver
{
	private $response;

	protected $api_url = '';
	protected $attachments = array();
	protected $notifications = array();
	protected $header = array
	(
		'Accept: application/xml',
		'Content-Type: application/xml'
	);

	/**
	 *	__construct
	 *
	 * @param array $config
	 * @return void
	 * @access public
	 *
	 */
	public function __construct($config = array())
	{
		$config += Kohana::config('service.basecamp');

		parent::__construct($config);
	}

	/**
	 * projects
	 *
	 * @param integer $id
	 *
	 */
	public function projects($id = '')
	{
		$url = (is_int($id)) ? '/projects/'.$id.'.xml' : '/projects.xml';

		$this->api_call($url);

		return $this->response;
	}

	/**
	 * people
	 *
	 * @param integer $company_id
	 * @param integer $project_id
	 *
	 */
	public function people($company_id, $project_id = '')
	{
		$url = (is_int($project_id)) ? '/projects/'.$project_id : '';

		$this->api_call($url.'/contacts/people/'.$company_id);

		return $this->response;
	}

	/**
	 * person
	 *
	 * @param integer $person_id
	 *
	 */
	public function person($person_id)
	{
		$this->api_call('/contacts/person/'.$person_id);

		return $this->response;
	}

	/**
	 * companies
	 *
	 * @param integer $project_id
	 *
	 */
	public function companies($project_id = '')
	{
		$url = (is_int($project_id)) ? '/projects/'.$project_id : '';

		$this->api_call($url.'/companies.xml');

		return $this->response;
	}

	/**
	 * company
	 *
	 * @param integer $company_id
	 *
	 */
	public function company($company_id)
	{
		$this->api_call('/companies/'.$company_id.'.xml');

		return $this->response;
	}

	/**
	 * create_category
	 *
	 * @param integer $project_id
	 * @param string $name
	 * @param string $type
	 *
	 */
	public function create_category($project_id, $name, $type = 'post')
	{
		$type = ($type == 'post') ? $type : 'attachment';

		$xml = View::factory('basecamp/category/create')
			->set(array('type' => $type, 'name' => $name));

		$this->post($xml)->api_call('/projects/'.$project_id.'/categories.xml', false);

		return $this->http_status;
	}

	/**
	 * destroy_category
	 *
	 * @param integer $id
	 *
	 */
	public function destroy_category($id)
	{
		$this->delete()->api_call('/categories/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * categories
	 *
	 * @param integer $project_id
	 * @param string $type
	 *
	 */
	public function categories($project_id, $type = '')
	{
		if ($type != '')
			$type = '?type='.(($type == 'post') ? $type : 'attachment');

		$this->api_call('/projects/'.$project_id.'/categories.xml'.$type);

		return $this->response;
	}

	/**
	 * category
	 *
	 * @param integer $id
	 *
	 */
	public function category($id)
	{
		$this->api_call('/categories/'.$id.'.xml');

		return $this->response;
	}

	/**
	 * update_category
	 *
	 * @param integer $id
	 * @param string $name
	 *
	 */
	public function update_category($id, $name)
	{
		$xml = View::factory('basecamp/category/update')
			->set('name', $name);

		$this->put($xml)->api_call('/categories/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * create_message
	 *
	 * @param integer $project
	 * @param integer $category
	 * @param string $title
	 * @param string $body
	 * @param string $extended
	 * @param boolean $private
	 *
	 */
	public function create_message($project, $category, $title, $body, $extended = '', $private = false)
	{
		$xml = View::factory('basecamp/message/create')->set(array
		(
			'category_id' 	=> $category,
			'title'			=> $title,
			'body'			=> $body,
			'extended_body'	=> $extended,
			'private'		=> $private,
			'notifications'	=> $this->notifications,
			'attachments'	=> $this->attachments
		));

		$this->post($xml)->api_call('/projects/'.$project_id.'/posts.xml', false);

		return $this->http_status;
	}

	/**
	 * destroy_message
	 *
	 * @param integer $id
	 *
	 */
	public function destroy_message($id)
	{
		$this->delete()->api_call('posts/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * edit_message
	 *
	 * @param integer $id
	 *
	 */
	public function edit_message($id)
	{
		$this->api_call('/posts/'.$id.'/edit.xml');

		return $this->response;
	}

	/**
	 * archived_messages
	 *
	 * @param integer $project_id
	 * @param integer $category_id
	 *
	 */
	public function archived_messages($project_id, $category_id = '')
	{
		$url = '/projects/'.$project_id.((is_int($category_id)) ? '/cat/'.$category_id : '').'/posts/archive.xml';

		$this->api_call($url);

		return $this->response;
	}

	/**
	 * message
	 *
	 * @param integer $id
	 *
	 */
	public function message($id)
	{
		$this->api_call('/posts/'.$id.'.xml');

		return $this->response;
	}

	/**
	 * messages
	 *
	 * @param integer $project_id
	 * @param integer $category_id
	 *
	 */
	public function messages($project_id, $category_id = '')
	{
		$url = '/projects/'.$project_id.((is_int($category_id)) ? '/cat/'.$category_id : '').'/posts.xml';

		$this->api_call($url);

		return $this->response;
	}

	/**
	 * new_message
	 *
	 * @param integer $project_id
	 *
	 */
	public function new_message($project_id)
	{
		$this->api_call('/projects/'.$project_id.'/posts/new.xml');

		return $this->response;
	}

	/**
	 * update_message
	 *
	 * @param integer $id
	 * @param integer $category
	 * @param string $title
	 * @param string $body
	 * @param string $extended
	 * @param boolean $private
	 *
	 */
	public function update_message($id, $category, $title, $body, $extended = '', $private = false)
	{
		$xml = View::factory('basecamp/message/create')->set(array
		(
			'category_id' 	=> $category,
			'title'			=> $title,
			'body'			=> $body,
			'extended_body'	=> $extended,
			'private'		=> $private,
			'notifications'	=> $this->notifications,
			'attachments'	=> $this->attachments
		));

		$this->put($xml)->api_call('/posts/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * create_comment
	 *
	 * @param string $resource
	 * @param string $resource_id
	 * @param string $body
	 *
	 */
	public function create_comment($resource, $resource_id, $body)
	{
		$xml = View::factory('basecamp/comment/create')->set('body', $body);

		$this->post($xml)->api_call('/'.$resource.'/'.$resource_id.'/comments.xml', false);

		return $this->http_status;
	}

	/**
	 * destroy_comment
	 *
	 * @param integer $id
	 *
	 */
	public function destroy_comment($id)
	{
		$this->delete()->api_call('/comments/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * edit_comment
	 *
	 * @param integer $id
	 *
	 */
	public function edit_comment($id)
	{
		$this->api_call('/comments/'.$id.'/edit.xml');

		return $this->response;
	}

	/**
	 * comments
	 *
	 * @param string $resource
	 * @param integer $resource_id
	 *
	 */
	public function comments($resource, $resource_id)
	{
		$this->api_call('/'.$resource.'/'.$resource_id.'/comments.xml');

		return $this->response;
	}

	/**
	 * comment
	 *
	 * @param integer $id
	 *
	 */
	public function comment($id)
	{
		$this->api_call('/comments/'.$id.'.xml');

		return $this->response;
	}

	/**
	 * new_comment
	 *
	 * @param string $resource
	 * @param integer $resource_id
	 *
	 */
	public function new_comment($resource, $resource_id)
	{
		$this->api_call('/'.$resource.'/'.$resource_id.'/comments/new.xml');

		return $this->response;
	}

	/**
	 * update_comment
	 *
	 * @param integer $id
	 *
	 */
	public function update_comment($id, $body)
	{
		$xml = View::factory('basecamp/comment/create')->set('body', $body);

		$this->put($xml)->api_call('/comments/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * create_todo_list
	 *
	 * @param integer $project_id
	 * @param string $name
	 * @param string $description
	 * @param integer $milestone_id
	 * @param boolean $private
	 * @param integer $template_id
	 *
	 */
	public function create_todo_list($project_id, $name, $description, $private = false, $tracked= false, $milestone_id = false, $template_id = false)
	{
		$data = array
		(
			'name' 			=> $name,
			'description'	=> $description,
			'private'		=> (string) $private,
			'tracked'		=> (string) $tracked
		);

		if (is_int($milestone_id))
			$data['milestone_id'] = $milestone_id;

		if (is_int($template_id))
			$data['template_id'] = $template_id;

		$xml = View::factory('basecamp/todo/list/create')->set($data);

		$this->post($xml)->api_call('/projects/'.$project_id.'/todo_lists.xml', false);

		return $this->http_status;
	}

	/**
	 * destroy_todo_list
	 *
	 * @param integer $id
	 *
	 */
	public function destroy_todo_list($id)
	{
		$this->delete()->api_call('/todo_lists/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * edit_todo_list
	 *
	 * @param integer $id
	 *
	 */
	public function edit_todo_list($id)
	{
		$this->api_call('/todo_lists/'.$id.'/edit.xml');

		return $this->response;
	}

	/**
	 * todo_lists
	 *
	 * @param mixed $responsible
	 *
	 */
	public function todo_lists($responsible = 'unassigned')
	{
		$id = ($responsible == 'unassigned') ? '' : $responsible;

		$this->api_call('/todo_lists.xml?responsible_party='.$id);

		return $this->response;
	}

	/**
	 * todo_project_lists
	 *
	 * @param integer $project_id
	 * @param string $filter
	 *
	 */
	public function todo_project_lists($project_id, $filter = 'all')
	{
		$this->api_call('/projects/'.$project_id.'/todo_lists.xml?filter='.$filter);

		return $this->response;
	}

	/**
	 * todo_list
	 *
	 * @param integer $id
	 *
	 */
	public function todo_list($id)
	{
		$this->api_call('/todo_lists/'.$id.'.xml');

		return $this->response;
	}

	/**
	 * new_todo_list
	 *
	 * @param integer $project_id
	 *
	 */
	public function new_todo_list($project_id)
	{
		$this->api_call('/projects/'.$project_id.'/todo_lists/new.xml');

		return $this->response;
	}

	/**
	 * reorder_todo_list
	 *
	 * @param integer $project_id
	 * @param array $ids
	 *
	 */
	public function reorder_todo_list($project_id, $ids)
	{
		$xml = View::factory('basecamp/todo/list/reorder')->set('ids', $ids);

		$this->post($xml)->api_call('/projects/'.$project_id.'/todo_lists/reorder.xml', false);

		return $this->last_status_code;
	}

	/**
	 * update_todo_list
	 *
	 * @param integer $id
	 *
	 */
	public function update_todo_list($id, $name, $description, $private = false, $tracked = false, $milestone_id = false)
	{
		$data = array
		(
			'name' 			=> $name,
			'description'	=> $description,
			'private'		=> (string) $private,
			'tracked'		=> (string) $tracked
		);

		if (is_int($milestone_id))
			$data['milestone_id'] = $milestone_id;

		$xml = View::factory('basecamp/todo/list/create')->set($data);

		$this->put($xml)->api_call('/todo_lists/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * complete_todo_item
	 *
	 * @param integer $id
	 *
	 */
	public function complete_todo_item($id)
	{
		$this->put()->api_call('/todo_items/'.$id.'/complete.xml', false);

		return $this->last_status_code;
	}

	/**
	 * create_todo_item
	 *
	 * @param integer $todo_list_id
	 * @param string $content
	 * @param integer $party_id
	 * @param boolean $notify
	 *
	 */
	public function create_todo_item($todo_list_id, $content, $party_id = '', $notify = false)
	{
		$data = array
		(
			'content' 	=> $content,
			'notify'	=> (string) $notify
		);

		if (is_int($party_id))
			$data['party_id'] = $party_id;

		$xml = View::factory('basecamp/todo/item/create', false)->set($data);

		$this->post($xml)->api_call('/todo_lists/'.$todo_list_id.'/todo_items.xml', false);

		return $this->http_status;
	}

	/**
	 * destroy_todo_item
	 *
	 * @param integer $id
	 *
	 */
	public function destroy_todo_item($id)
	{
		$this->delete()->api_call('/todo_items/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * edit_todo_item
	 *
	 * @param integer $id
	 *
	 */
	public function edit_todo_item($id)
	{
		$this->api_call('/todo_items/'.$id.'/edit.xml');

		return $this->response;
	}

	/**
	 * todo_items
	 *
	 * @param integer $todo_list_id
	 *
	 */
	public function todo_items($todo_list_id)
	{
		$this->api_call('/todo_lists/'.$todo_list_id.'/todo_items.xml');

		return $this->response;
	}

	/**
	 * todo_item
	 *
	 * @param integer $id
	 *
	 */
	public function todo_item($id)
	{
		$this->api_call('/todo_items/'.$id.'.xml');

		return $this->response;
	}

	/**
	 * new_todo_item
	 *
	 * @param integer $todo_list_id
	 *
	 */
	public function new_todo_item($todo_list_id)
	{
		$this->api_call('/todo_lists/'.$todo_list_id.'/todo_items/new.xml');

		return $this->response;
	}

	/**
	 * reorder_todo_items
	 *
	 * @param integer $todo_list_id
	 * @param array $ids
	 *
	 */
	public function reorder_todo_items($todo_list_id, $ids)
	{
		$xml = View::factory('basecamp/todo/item/reorder')->set('ids', $ids);

		$this->post($xml)->api_call('/todo_lists/#{todo_list_id}/todo_items/reorder.xml', false);

		return $this->http_status;
	}

	/**
	 * uncomplete_todo_item
	 *
	 * @param integer $id
	 *
	 */
	public function uncomplete_todo_item($id)
	{
		$this->put()->api_call('/todo_items/'.$id.'/uncomplete.xml', false);

		return $this->http_status;
	}

	/**
	 * update_todo_item
	 *
	 * @param integer $id
	 * @param string $content
	 * @param integer $party_id
	 * @param boolean $notify
	 *
	 */
	public function update_todo_item($id, $content, $party_id = '', $notify = false)
	{
		$data = array
		(
			'content' 	=> $content,
			'notify'	=> (string) $notify
		);

		if (is_int($party_id))
			$data['party_id'] = $party_id;

		$xml = View::factory('basecamp/todo/item/create', false)->set($data);

		$this->put($xml)->api_call('/todo_items/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * complete_milestone
	 *
	 * @param integer $id
	 *
	 */
	public function complete_milestone($id)
	{
		$this->put()->api_call('/milestones/complete/'.$id, false);

		return $this->http_status;
	}

	/**
	 * create_milestone
	 *
	 * @param integer $project_id
	 * @param mixed $milestone
	 * @param string $deadline
	 * @param string $responsible_id
	 * @param boolean $notify
	 *
	 */
	public function create_milestone($project_id, $milestone, $deadline, $responsible_id, $notify = false)
	{
		if ( ! is_array($milestone))
			$milestone[] = array
			(
				'title' 	=> $milestone,
				'deadline'	=> $deadline,
				'id'		=> $responsible_id,
				'notify'	=> (string) $notify
			);

		$xml = View::factory('basecamp/milestone/create')->set('milestones', $milestone);

		$this->post($xml)->api_call('/projects/'.$project_id.'/milestones/create', false);

		return $this->http_status;
	}

	/**
	 * create_tracker
	 *
	 * @param integer $type_id
	 * @param string $type
	 * @param integer $person_id
	 * @param string $date
	 * @param integer $hours
	 * @param string $description
	 *
	 */
	public function create_tracker($type_id, $type, $person_id, $date, $hours, $description)
	{
		$data = array
		(
			'person_id'		=> $person_id,
			'date'			=> $date,
			'hours'			=> $hours,
			'description'	=> $description
		);

		$type = ($type == 'projects') ? $type : 'todo_items';

		$xml = View::factory('basecamp/tracker/create')->set($data);

		$this->post($xml)->api_call('/'.$type.'/'.$type_id.'/time_entries.xml', false);

		return $this->http_status;
	}

	/**
	 * destroy_tracker
	 *
	 * @param integer $id
	 *
	 */
	public function destroy_tracker($id)
	{
		$this->delete()->api_call('/time_entries/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * edit_tracker
	 *
	 * @param integer $id
	 *
	 */
	public function edit_tracker($id)
	{
		$this->api_call('/time_entries/'.$id.'/edit.xml');

		return $this->response;
	}

	/**
	 * trackers
	 *
	 * @param integer $type_id
	 * @param string $type
	 *
	 */
	public function trackers($type_id, $type)
	{
		$type = ($type == 'projects') ? $type : 'todo_items';

		$this->api_call('/'.$type.'/'.$type_id.'/time_entries.xml');

		return $this->response;
	}

	/**
	 * tracker
	 *
	 * @param integer $id
	 *
	 */
	public function tracker($id)
	{
		$this->api_call('/time_entries/'.$id.'.xml');

		return $this->response;
	}

	/**
	 * new_tracker
	 *
	 * @param integer $project_id
	 *
	 */
	public function new_tracker($project_id)
	{
		$this->api_call('/projects/'.$project_id.'/time_entries/new.xml');

		return $this->response;
	}

	/**
	 * tracker_report
	 *
	 */
	public function tracker_report()
	{
		// TODO
	}

	/**
	 * update_tracker
	 *
	 * @param integer $id
	 * @param integer $person_id
	 * @param string $date
	 * @param integer $hours
	 * @param string $description
	 * @param integer $todo_id
	 *
	 */
	public function update_tracker($id, $person_id, $date, $hours, $description, $todo_id = '')
	{
		$data = array
		(
			'person_id'		=> $person_id,
			'date'			=> $date,
			'hours'			=> $hours,
			'description'	=> $description
		);

		if (is_int($todo_id))
			$data['todo'] = $todo_id;

		$xml = View::factory('basecamp/tracker/create')->set($data);

		$this->put($xml)->api_call('/time_entries/'.$id.'.xml', false);

		return $this->http_status;
	}

	/**
	 * upload
	 *
	 * @param mixed $file
	 * @param string $name
	 * @param string $type
	 *
	 */
	public function upload($file, $name = null, $type = null)
	{
		if ( ! is_array($file))
			$file = array($file => array($name, $type));

		foreach($file as $path => $data)
		{
			if ( ! file_exists($path) or ! is_readable($path))
				throw new Kohana_User_Exception('Basecamp Driver', Kohana::lang('service.file_error', $path));

			$this->header = array
			(
				'Accept: application/xml',
				'Content-Type: application/octet-stream',
				'Content-Length: '.filesize($path)
			);

			$response = $this->post(file_get_contents($path))->api_call('/upload/');

			$this->attachments[] = array
			(
			    'name'		=> $data[0],
			    'basename'	=> end(explode('/', $path)),
			    'mime'		=> $data[1],
			    'id'		=> $response->upload->id,
			);
		}

		return $this;
	}

	/**
	 * add_notifications
	 *
	 * @param integer $id
	 *
	 */
	public function add_notifications($id)
	{
		if (is_int($id))
			$this->notifications[] = $id;

		return $this;
	}

	/**
	 * response
	 *
	 */
	public function response()
	{
		return $this->response;
	}

	/**
	 * header
	 *
	 */
	protected function header()
	{
		return $this->header;
	}

	/**
	 * api_call
	 *
	 * @param string $api_url
	 * @param boolean $xml_response
	 * @return mixed
	 * @access protected
	 *
	 */
	protected function api_call($api_url, $xml_response = true)
	{
		if ( ! $result = parent::api_call($api_url, true))
			throw new Kohana_User_Exception('Basecamp Driver', Kohana::lang('service.invalid_response', $this->last_api_call(), $this->last_status_code()));

		if ($xml_response === true)
		{
			$xml = simplexml_load_string($result);

			if (false === $xml)
				throw new Kohana_User_Exception('Basecamp Driver', Kohana::lang('service.xml_parse_error'));

			$this->response = $xml;
		}
	}

} //End Basecamp Driver
